﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Weka
{

    internal class FieldSerialiserProviderGen<TMessage, TFieldSerialiser, TDT> : FieldSerialiserProvider<TMessage, TDT>
        where TFieldSerialiser : FieldSerialiser<TMessage, TDT>, new()
    {
        public override FieldSerialiser<TMessage> CreateFieldSerialiser()
        {
            return new TFieldSerialiser() { Reader = Reader, FieldInfo = FieldInfo };
        }
    }

    internal class FieldSerialiserProviderGenNN<TMessage, TFieldSerialiser, TDT> : FieldSerialiserProvider<TMessage, TDT>
        where TFieldSerialiser : FieldSerialiser<TMessage, Nullable<TDT>>, new()
        where TDT : struct
    {
        public override FieldSerialiser<TMessage> CreateFieldSerialiser()
        {
            return new TFieldSerialiser() { Reader = v => Reader(v), FieldInfo = FieldInfo };
        }
    }


    internal class FieldSerialiserInt32N<T> : FieldSerialiser<T, Int32?>
    {
        private int? _value;

        public override void ReadValueFromTAndWriteHeader(T value, StreamWrita sw)
        {
            ReadValueFromTAndWriteHeaderProtected(Reader(value), sw);
        }
        protected void ReadValueFromTAndWriteHeaderProtected(Int32? value, StreamWrita sw)
        {
            if (!(_value = value).HasValue)
            {
                sw.HeaderWrite00();
            }
            else
            {
                Int32 v = _value.Value;
                if ((v & 0xFF) == v)
                    sw.HeaderWrite01();
                else if ((v & 0xFFFF) == v)
                    sw.HeaderWrite10();
                else
                    sw.HeaderWrite11();
            }
        }
        public override void WriteValue(StreamWrita sw)
        {
            if (!_value.HasValue) return;

            Int32 v = _value.Value;
            if ((v & 0xFF) == v)
                sw.Write((byte)v);
            else if ((v & 0xFFFF) == v)
                sw.Write((UInt16)v);
            else
                sw.Write(v);
        }
    }
    internal class FieldSerialiserProviderInt32<T> : FieldSerialiserProvider<T, Int32>
    {
        public override FieldSerialiser<T> CreateFieldSerialiser()
        {
            return new FieldSerialiserInt32<T>() { Reader = Reader, FieldInfo = FieldInfo };
        }
    }

    internal class FieldSerialiserInt32<T> : FieldSerialiserInt32N<T>
    {
        public new Func<T, Int32> Reader { get; set; }
        public override void ReadValueFromTAndWriteHeader(T value, StreamWrita streamWriter)
        {
            ReadValueFromTAndWriteHeaderProtected(Reader(value), streamWriter);
        }
    }


    internal class FieldSerialiserBoolean<T> : FieldSerialiser<T, bool>
    {
        public override void ReadValueFromTAndWriteHeader(T value, StreamWrita sw)
        {
            if (Reader(value))
                sw.HeaderWrite1();
            else
                sw.HeaderWrite0();
        }
        public override void WriteValue(StreamWrita sw) { }
    }


    internal class FieldSerialiserProviderBooleanN<T> : FieldSerialiserProvider<T, Boolean?>
    {
        public override FieldSerialiser<T> CreateFieldSerialiser() { return new FieldSerialiserBooleanN<T>() { Reader = Reader, FieldInfo = FieldInfo }; }
    }
    internal class FieldSerialiserBooleanN<T> : FieldSerialiser<T, Boolean?>
    {
        public override void ReadValueFromTAndWriteHeader(T value, StreamWrita sw)
        {
            bool? v = Reader(value);
            if (!v.HasValue)
            {
                sw.HeaderWrite0();
                return;
            }

            sw.HeaderWrite1();

            if (v.Value)
                sw.HeaderWrite1();
            else
                sw.HeaderWrite0();
        }
        public override void WriteValue(StreamWrita sw) { }
    }



    internal class FieldSerialiserString<T> : FieldSerialiser<T, string>
    {
        private string _value;
        private int _byteCount;
        public override void ReadValueFromTAndWriteHeader(T value, StreamWrita sw)
        {
            if ((_value = Reader(value)) == null)
            {
                sw.HeaderWrite00();
                _byteCount = 0;
                return;
            }

            if (_value.Length == 0)
            {
                sw.HeaderWrite01();
                _byteCount = 0;
                return;
            }

            _byteCount = UTF8Encoding.UTF8.GetByteCount(_value);

            if (_byteCount < 257)
                sw.HeaderWrite10();
            else
                sw.HeaderWrite11();
        }
        public override void WriteValue(StreamWrita sw)
        {
            if (_byteCount == 0) return;

            if (_byteCount < 257)
                sw.Write((byte)(_byteCount - 1));
            else
                sw.Write(_byteCount);

            sw.WriteStringRaw(_value, _byteCount);
        }
    }

    internal class FieldSerialiserProviderString<T> : FieldSerialiserProvider<T, string>
    {
        public override FieldSerialiser<T> CreateFieldSerialiser() { return new FieldSerialiserString<T>() { Reader = Reader, FieldInfo = FieldInfo }; }
    }






    internal class FieldSerialiserProviderDateTimeN<T> : FieldSerialiserProvider<T, DateTime?>
    {
        public override FieldSerialiser<T> CreateFieldSerialiser() { return new FieldSerialiserDateTimeN<T>() { Reader = Reader, FieldInfo = FieldInfo }; }
    }
    internal class FieldSerialiserDateTimeN<T> : FieldSerialiser<T, DateTime?>
    {
        private DateTime? _value;

        private static readonly DateTime _minDate = new DateTime(1950, 1, 1);
        private static readonly DateTime _maxDate = _minDate.AddDays(UInt16.MaxValue / 2);

        public override void ReadValueFromTAndWriteHeader(T value, StreamWrita sw)
        {
            ReadValueFromTAndWriteHeaderProtected(Reader(value), sw);
        }
        protected void ReadValueFromTAndWriteHeaderProtected(DateTime? value, StreamWrita sw)
        {
            _value = value;
            if (!_value.HasValue)
            {
                sw.HeaderWrite00();
                return;
            }

            DateTime v = _value.Value;
            if (v.Kind == DateTimeKind.Local)
                sw.HeaderWrite01();
            else if (v.Kind == DateTimeKind.Utc)
                sw.HeaderWrite10();
            else if (v.Kind == DateTimeKind.Unspecified)
                sw.HeaderWrite11();

            if (IsDayOnly(v))
                sw.HeaderWrite00();
            else if (IsDayWithShortTime(v))
                sw.HeaderWrite10();
            else
                sw.HeaderWrite1();
        }

        public override void WriteValue(StreamWrita sw)
        {
            if (!_value.HasValue) return;

            DateTime v = _value.Value;

            if (IsDayOnly(v))
            {
                sw.Write((UInt16)v.Subtract(_minDate).TotalDays);
            }
            else if (IsDayWithShortTime(v))
            {
                UInt16 v2 = ((UInt16)(((UInt16)v.Subtract(_minDate).TotalDays) * 2));
                if (v.Hour >= 12) v2++;
                sw.Write(v2);

                TimeSpan ts = (v - v.Date);
                if (ts.Hours >= 12) ts = ts.Subtract(new TimeSpan(12, 0, 0));
                sw.Write((byte)(((int)ts.TotalMinutes) / 5));
            }
            else
            {
                sw.Write(v.Ticks);
            }
        }
        private bool IsDayOnly(DateTime v)
        {
            return (v.Date == v && v >= _minDate && v < _maxDate);
        }

        private bool IsDayWithShortTime(DateTime v)
        {
            return (v.Millisecond == 0 && v.Second == 0 && (v.Minute % 5 == 0) && v >= _minDate && v < _maxDate);
        }
    }


    internal class FieldSerialiserDecimal<T> : FieldSerialiser<T, decimal?>
    {
        private int[] _data;
        public override void ReadValueFromTAndWriteHeader(T value, StreamWrita streamWriter)
        {
            decimal? v = Reader(value);
            if (v.HasValue)
            {
                streamWriter.HeaderWrite1();
                _data = decimal.GetBits(v.Value);
                int usedByteCount = GetUsedByteCount(_data);
                streamWriter.HeaderWrite1Bits((_data[3] >> 31)&0x01);//Is Negative
                streamWriter.HeaderWrite4Bits(usedByteCount);
            }
            else
            {
                streamWriter.HeaderWrite0();
                _data = null;
            }
        }

        public override void WriteValue(StreamWrita streamWriter)
        {
            if (_data == null) return;

            streamWriter.Write((byte)((_data[3] >> 16) & 0xFF));//Write exponent 

            int usedByteCount = GetUsedByteCount(_data);
            for (int i = 0; i < usedByteCount; i++)
            {
                byte b = (byte)(_data[i >> 2] >> (((i & 3)) << 3));
                streamWriter.Write(b);
            }
        }
        private static int GetUsedByteCount(int[] data)
        {
            for (int i = 11; i >= 0; i--)
            {
                int v1 = data[i >> 2];
                int v2 = v1 >> ((i & 3) << 3);
                if (v2 != 0) return i + 1;
            }
            return 0;
        }

    }




    internal class FieldSerialiserFuncStream<T> : FieldSerialiser<T, Func<Stream>>
    {
        private Func<Stream> _value;

        public override void ReadValueFromTAndWriteHeader(T value, StreamWrita sw)
        {
            _value = Reader(value);
        }
        public override void WriteValue(StreamWrita sw)
        {
            if (_value == null)
            {
                sw.Write((byte)0);
                return;
            }



            //TODO            sw.WriteStringRaw(_value);
        }
    }

    internal class FieldSerialiserProviderFuncStream<T> : FieldSerialiserProvider<T, Func<Stream>>
    {
        public override FieldSerialiser<T> CreateFieldSerialiser() { return new FieldSerialiserFuncStream<T>() { Reader = Reader, FieldInfo = FieldInfo }; }
    }


}
